<?php
/**
 * Parser for variables, tags, and values resolution
 *
 * @author André Silva
 */
class Parser {
	/**
	 * Return a value based in a virtual path
	 * @param object $object
	 * @param string $param
	 * @return mixed
	 */
	public static function getValue(&$object, $param) {
		$pieces = explode('.', $param);
		if ($pieces[0] == 'controller') { // prefix controller.*
			array_shift($pieces);
			$param = Cerberus::getController();
		} elseif ($pieces[0] == 'session' && Cerberus::moduleExists('Session')) { // prefix session.*
			array_shift($pieces);
			$param = Session::get(array_shift($pieces));
		} else {
			$param = $object;
		}
		foreach ($pieces as $piece) {
			if (is_array($param)) {
				if ($param[$piece] !== null) {
					$param = $param[$piece];
				} else {
					$param = null;
					break;
				}
			} else {
				if ($param->$piece !== null) {
					$param = $param->$piece;
				} else {
					$param = null;
					break;
				}
			}
		}
		return $param;
	}

	/**
	 * Parse all tags inside the buffer
	 * @param string $buffer
	 * @param object $parent
	 */
	public static function parseTags(&$buffer, &$parent) {
		$blocks = array();
		while (($tag = Parser::getTag($buffer))) {

			$type = $tag['type'];

			// Skip block tags
			if ($type == 'block') {
				$blocks[] = $tag['tag'];
				$buffer = str_replace($tag['tag'], '#block' . count($blocks) . '#', $buffer);
				continue;
			}

			$type = ucfirst($type).'Tag';
			$component = new $type($parent, $tag['content'], $tag['properties']);

			$component->parse();

			$buffer = str_replace($tag['tag'], $component->getContent(), $buffer);
		}

		// Return block tags
		while (!empty($blocks)) {
			$buffer = str_replace('#block' . count($blocks) . '#', array_pop($blocks), $buffer);
		}
	}

	/**
	 * Search for a single output inside the buffer
	 * @param string $buffer
	 * @return array
	 */
	public static function getVariable($buffer) {
		$var_start = '${';
		$var_end = '}';

		$offset = strpos($buffer, $var_start);

		if ($offset === false) {
			return false;
		}
		$var = substr($buffer, $offset);

		$var = substr($var, 2, strpos($var, $var_end) - 2);

		return array('name' => $var, 'tag' => $var_start . $var . $var_end);
	}

	/**
	 * Return the first tag found in the buffer using the pattern: <php:type />
	 * @param string $buffer
	 * @param string $type
	 * @param boolean $html
	 * @return array
	 */
	public static function getTag($buffer, $type = '', $html = false) {
		$tag = null;
		$content = null;
		$properties_buffer = null;
		$prefix = ($html) ? '' : CERBERUS_TAG . ':';

		if (empty($type)) {
			$tag = substr($buffer, strpos($buffer, '<' . $prefix));
			$pos = strpos($tag, '<' . $prefix);

			if ($pos === false) {
				return false;
			}

			$tag = substr($tag, $pos + 1, strpos($tag, ' ') - $pos - 1);

			// Check if tag is like <php:type>
			if (strpos($tag, '>') !== false) {
				$tag = substr($tag, 0, strpos($tag, '>'));
			}

			$type = substr($tag, (($html) ? 0 : strpos($tag, ':')) + 1);
		} else {
			$tag = $prefix . $type;
		}

		$tag_start = '<' . $tag;
		$tag_end = '</' . $tag . '>';
		$tag_start_size = strlen($tag_start);
		$tag_end_size = strlen($tag_end);

		$offset = stripos($buffer, $tag_start);

		if ($offset === false) {
			return false;
		}

		$tag = substr($buffer, $offset);
		$tag = substr($tag, 0, strpos($tag, '>'));

		if ($tag[strlen($tag)-1] !== '/') {
			$close_tag = stripos($buffer, $tag_end);

			if($close_tag === false) {
				throw new Exception('Tag not closed: '.$tag);
			}

			$end = $offset;
			$count = substr_count($buffer, $tag_start);
			$content = $buffer;
			$replace_start = str_repeat('#', $tag_start_size);
			$replace_end = str_repeat('#', $tag_end_size);

			while ($count--) {
				$end = stripos($content, $tag_end, $end);

				$test = substr($content, $offset+1, $end - $offset-1);
				$inner = stripos($test, $tag_start);

				if($inner === false) {
					break;
				}

				$content = substr_replace($content, $replace_start, $inner, $tag_start_size);
				$content = substr_replace($content, $replace_end, $end, $tag_end_size);
				$test = substr($content, $inner, $tag_start_size);
			}
			// Full tag content
			$tag = substr($buffer, $offset, $end - $offset + $tag_end_size);

			// Inner tag content
			$content = substr($tag, stripos($tag, '>') + 1);
			$content = substr($content, 0, strlen($content) - $tag_end_size);

			$properties_buffer = trim(substr($tag, strlen($tag_start)));
			$properties_buffer = trim(substr($properties_buffer, 0, stripos($properties_buffer, '>')));
		} else {
			// Self closing tag
			$tag = substr($buffer, $offset);
			$tag = substr($tag, 0, strpos($tag, '/>') + 2);

			$content = null;

			$properties_buffer = trim(substr($tag, strlen($tag_start)));
			$properties_buffer = trim(substr($properties_buffer, 0, stripos($properties_buffer, '/>')));
		}

		// Properties
		$properties = array();

		if (!empty($properties_buffer)) {
			if (preg_match_all('#([a-z]+)="?([^"]*)"?#i', $properties_buffer, $_properties)) {
				$props = count($_properties[0]);
				for ($y = 0; $y < $props; $y++) {
					$properties[$_properties[1][$y]] = $_properties[2][$y];
				}
			}
		}
		return array('type' => $type, 'properties' => $properties, 'tag' => $tag, 'content' => $content);
	}
}
?>
